﻿using System;
using System.Collections.Generic;
using System.Text;

using Microsoft.Xna.Framework;

using Daybreak.Core.Rendering;
using Daybreak.Core.SceneGraph;

namespace Daybreak.Core
{
    public partial class Scene
    {
        /// <summary>
        /// The controller object categorizes the declaration of generic control information. 
        /// A controller is a device or mechanism that manages and directs the operations of another object. 
        /// </summary>
        public abstract class Controller
        {
            /// <summary>
            /// The instance_controller element declares the instantiation of a COLLADA controller resource. 
            /// </summary>
            public class Instance : Drawable.Container
            {
                public Skin Skin;
                public Matrix[] CachedJointTransforms;

                /// <summary>
                /// Indicates where a skin controller is to start to search for the joint nodes it needs.
                /// </summary>
                public Transform[] SkeletonNodes;

                public Instance(Skin skin, Scene.Material.Binding[] bindings)
                {
                    Skin = skin;
                    Bindings = bindings;
                    CachedJointTransforms = new Matrix[skin.Joints.Count];
                }

                public override void CacheNodeTransform(Transform transformNode, ref Matrix transformMatrix)
                {
                    int idx = Skin.Joints.IndexOf(transformNode);

                    if (idx == -1)
                        return;

                    CachedJointTransforms[idx] = transformMatrix;
                }


                public override void MergeBounding(ref BoundingBox result)
                {

                }

                public override unsafe void CopyControlVerticesTo(ref float* rawVertexData, int vIndex)
                {
                    for (int i = 0; i < Skin.Assignments.Length; i++)
                    {
                        *rawVertexData++ = (float)Skin.Assignments[i].BoneIndices[vIndex];
                        *rawVertexData++ = Skin.Assignments[i].BoneWeights[vIndex];
                    }
                }

                public override string ToString()
                {
                    if (Skin == null)
                        return "Empty Skin Controller instance";

                    return string.Format("<instance_controller url='#{0}'>  joints:{1}", Skin.Name, Skin.Joints.Count);
                }


                public override void GetGeometryData(RenderItem slot, Drawable item)
                {

                }

                public override void GetWorldTransforms(List<Matrix> matrices)
                {
                    matrices.AddRange(CachedJointTransforms);
                }

                public override IMaterial GetMaterial(Drawable item)
                {
                    // TODO: implement
                    return null;
                }
            }

            /// <summary>
            /// The skin object contains vertex and primitive information sufficient to describe blend-weight skinning. 
            /// </summary>
            public class Skin : Controller
            {
                public class VertexAssignments
                {
                    /// <summary>
                    /// Per vertex bone weights
                    /// </summary>
                    public float[] BoneWeights;
                    /// <summary>
                    /// Per vertex bone indices
                    /// </summary>
                    public int[] BoneIndices;

                    public VertexAssignments(int vertexCount)
                    {
                        BoneWeights = new float[vertexCount];
                        BoneIndices = new int[vertexCount];

                    }

                    public void SetWeights(float weight, int start, int count)
                    {
                        if (BoneWeights == null)
                            return;

                        for (int i = 0; i < count; i++)
                            BoneWeights[i + start] = weight;
                    }

                    public void SetIndices(int index, int start, int count)
                    {
                        if (BoneIndices == null)
                            return;

                        for (int i = 0; i < count; i++)
                            BoneIndices[i + start] = index;
                    }

                }

                public Mesh.DataSlot Slot;
                /// <summary>
                /// Aggregates the per-joint information needed for this skin.
                /// </summary>
                public List<Transform> Joints = new List<Transform>();

                /// <summary>
                /// Provides extra information about the position and orientation of the base mesh before binding.
                /// </summary>
                public Matrix BindShapeMatrix;

                /// <summary>
                /// Per joint bind pose matrix array
                /// </summary>
                public Matrix[] BindPoses;

                /// <summary>
                /// Up to 4 bone assignment per vertex
                /// </summary>
                public VertexAssignments[] Assignments;

                /// <summary>
                /// Reference to the base mesh list
                /// </summary>
                public List<Geometry> SourceMeshList = new List<Geometry>();

                public Skin(string name)
                    : base(name)
                {
                }

                public Instance CreateInstance(Scene scene, Scene.Material.Binding[] bindings)
                {
                    Instance result = new Instance(this, bindings);

                    return result;
                }

                public void Build(List<KeyValuePair<Geometry, Transform>> geometryToDofMap)
                {
                    if (geometryToDofMap == null)
                        return;

                    SourceMeshList.Clear();
                    Joints.Clear();

                    // allocate geometry slot to store shared vertex & index buffers
                    Slot = new Mesh.DataSlot();
                    List<int> offsetList = new List<int>(geometryToDofMap.Count + 1);
                    List<int> countList = new List<int>(geometryToDofMap.Count + 1);
                    List<int> indexList = new List<int>(geometryToDofMap.Count + 1);

                    for (int i = 0; i < geometryToDofMap.Count; i++)
                    {
                        KeyValuePair<Geometry, Transform> pair = geometryToDofMap[i];

                        if (pair.Key.Mesh == null)
                            continue;

                        if (!Joints.Contains(pair.Value))
                            Joints.Add(pair.Value);

                        indexList.Add(Joints.IndexOf(pair.Value));

                        offsetList.Add(Slot.vertexCount);
                        pair.Key.Mesh.SetGeometrySlot(Slot);
                        SourceMeshList.Add(pair.Key);
                        countList.Add(pair.Key.Mesh.IndexCount);
                    }

                    Assignments = new VertexAssignments[1];
                    Assignments[0] = new VertexAssignments(Slot.vertexCount);
                    Assignments[0].SetWeights(1, 0, Slot.vertexCount);

                    BindShapeMatrix = Matrix.Identity;
                    BindPoses = new Matrix[Joints.Count];

                    for (int i = 0; i < BindPoses.Length; i++)
                        BindPoses[i] = Matrix.Identity;

                    for (int i = 0; i < offsetList.Count; i++)
                    {
                        Assignments[0].SetIndices(indexList[i], offsetList[i], countList[i]);
                    }

                }

                public override int AdditionalVertexElements
                {
                    get
                    {
                        if (Assignments == null)
                            return 0;

                        return Assignments.Length * 2;
                    }
                }

            }

            public string Name;

            public Controller(string name)
            {
                Name = name;
            }

            /// <summary>
            /// Gets per vertex additional elements number
            /// </summary>
            public abstract int AdditionalVertexElements { get; }

        }

        /// <summary>
        /// The library_controller class declares a module of controller elements. 
        /// </summary>
        public class ControllerLib
        {
            public List<Controller> ControllerList = new List<Controller>();

            public ControllerLib() { }

            public Controller FindByName(string name)
            {
                for (int i = 0; i < ControllerList.Count; i++)
                    if (ControllerList[i].Name == name)
                        return ControllerList[i];

                return null;

            }

        }

    }
}
